#include "perlin.hh"

PerlinNoise::PerlinNoise(int size, int seed)
{
  srand (time((time_t *)seed));

  img_ = new Map (size, size);
  for (int i = 0; i < img_->width_get (); ++i)
    for (int j = 0; j < img_->width_get (); ++j)
      (*img_) (i, j, (rand () % 156) + 60);
}

PerlinNoise::~PerlinNoise()
{
  vect_.clear ();
  pers_.clear ();
}

Map*
PerlinNoise::perform (double pers, int nb_oct)
{
  double pers_sum = 0;

  for (int k = 0; k < nb_oct; ++k)
    {
      vect_.push_back (new Map (img_->width_get (), img_->width_get ()));
      pers_.push_back (powf (pers, k + 1));

      pers_sum += pers_[k];

      for (int i = 0; i < img_->width_get (); ++i)
	for (int j = 0; j < img_->width_get (); ++j)
	  (*(vect_[k])) (i, j, (rand () % 156) + 60);

      for (int i = 0; i < img_->width_get (); ++i)
	for (int j = 0; j < img_->width_get (); ++j)
	  (*(vect_[k])) (i, j, val_interpo (i, j, pow (4, k),
					   img_));
    }

  Map* res = new Map (img_->width_get (), img_->width_get ());

  for (int i = 0; i < img_->width_get (); ++i)
    for (int j = 0; j < img_->width_get (); ++j)
      {
	for (int k = 0; k < nb_oct; ++k)
	  (*res) (i, j, (*res) (i, j) + (int) (((double) (*vect_[k]) (i, j)) * pers_[k]));

	(*res) (i, j, (*res) (i, j) / pers_sum);
      }

  smooth (res);

  return res;
}

int
PerlinNoise::pow (int x, int i)
{
  if (i == 0)
    return 1;
  else
    return (x * pow (x, i - 1));
}

double
PerlinNoise::powf (double x, int i)
{
  if (i == 0)
    return 1;
  else
    return (x * powf (x, i - 1));
}

double
PerlinNoise::val_interpo (int x, int y, int f, Map* img)
{
  int length = img->width_get ();
  double pas = (double) length / (double) f;
  int q = (double) x / pas;
  int borne1x = (double) q * pas;
  int borne2x = 0;

  borne2x = (double) (q + 1) * pas;

  if (borne2x >= length)
    borne2x = length - 1;

  int q2 = (double) y / pas;
  int borne1y = (double) q2 * pas;
  int borne2y = 0;

  borne2y = (double) (q2 + 1) * pas;

  if (borne2y >= length)
    borne2y = length - 1;

  int b00 = (*img) (borne1x, borne1y);
  int b01 = (*img) (borne1x, borne2y);
  int b10 = (*img) (borne2x, borne1y);
  int b11 = (*img) (borne2x, borne2y);

  int v1 = interpolate (b00, b01, borne2y - borne1y, y - borne1y);
  int v2 = interpolate (b10, b11, borne2y - borne1y, y - borne1y);

  return interpolate (v1, v2, borne2x - borne1x, x - borne1x);
}

int
PerlinNoise::interpolate (int y1, int y2, int n, int delta)
{
  if (n == 0)
    return y1;
  else if (n == 1)
    return y2;
  else
    {
      double a = (double) delta / (double) n;
      double v1 = 3 * powf (1 - a, 2) - 2 * powf (1 - a, 3);
      double v2 = 3 * powf (a, 2) - 2 * powf (a, 3);
      return (int) ((double) y1 * v1 + (double) y2 * v2);
    }
}

void
PerlinNoise::smooth (Map* map)
{
  // we get better contrast between floor and cliffs
  for (int i = 0; i < map->width_get (); ++i)
    for (int j = 0; j < map->width_get (); ++j)
      if ((*map) (i, j) > 90 && (*map) (i, j) < 150)
	(*map) (i, j, (*map) (i, j) - 50);

  // we first smooth all the image
  for (int turn = 0; turn < 5; ++turn)
    for (int i = 1; i < map->width_get () - 1; ++i)
      for (int j = 1; j < map->height_get () - 1; ++j)
	(*map) (i, j, ((*map) (i, j) + (*map) (i + 1, j) + (*map) (i - 1, j)
		       + (*map) (i, j - 1) + (*map) (i + 1, j - 1) + (*map) (i - 1, j - 1)
		       + (*map) (i, j + 1) + (*map) (i + 1, j + 1) + (*map) (i - 1, j + 1)) / 9);

  // then we align borders of the map
  for (int i = 0; i < map->width_get (); ++i)
    {
      (*map) (i, 0, (*map) (i, 4));
      (*map) (i, map->width_get () - 1, (*map) (i, map->width_get () - 5));
    }
  for (int i = 0; i < map->width_get (); ++i)
    {
      (*map) (0, i, (*map) (4, i));
      (*map) (map->width_get () - 1, i, (*map) (map->width_get () - 5, i));
    }

  // we reduce low frequences by smoothing it
  for (int turn = 0; turn < 10; ++turn)
    for (int i = 1; i < map->width_get () - 1; ++i)
      for (int j = 1; j < map->height_get () - 1; ++j)
	if ((*map) (i, j) < 130)
	  (*map) (i, j, ((*map) (i, j) + (*map) (i + 1, j) + (*map) (i - 1, j)
			 + (*map) (i, j - 1) + (*map) (i + 1, j - 1) + (*map) (i - 1, j - 1)
			 + (*map) (i, j + 1) + (*map) (i + 1, j + 1) + (*map) (i - 1, j + 1)) / 9);

  // and then smooth a last time to make cliffs better looking
  for (int turn = 0; turn < 10; ++turn)
    for (int i = 1; i < map->width_get () - 1; ++i)
      for (int j = 1; j < map->height_get () - 1; ++j)
	(*map) (i, j, ((*map) (i, j) + (*map) (i + 1, j) + (*map) (i - 1, j)
		       + (*map) (i, j - 1) + (*map) (i + 1, j - 1) + (*map) (i - 1, j - 1)
		       + (*map) (i, j + 1) + (*map) (i + 1, j + 1) + (*map) (i - 1, j + 1)) / 9);
}
